/**
 * Copyright(c) by JNet Smartware Group.
 * All rights reserved. Not distribution or redistribution without
 * any permission of JNet Smartware Group.
 * 
 * @author thuc
 * 2010/03/15
 * Program: BeanUtil.java
 */
package vn.smartware.co.java.net.commons;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;


/**
 * 
 * <p>
 * Utility class for populating JavaBeans properties via reflection.
 * </p>
 * 
 * @author Thien
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class BeanUtil extends BeanUtils {
	
	/**
	 * field hash clazz prop list <br>
	 */
	private static Map hashClazzPropList = Collections
			.synchronizedMap(new HashMap());
	
	/**
	 * simple property (primitive, primitive wrapper, array)
	 */
	public static final Byte SIMPLE_PROPERTY = new Byte("1");

	/**
	 * List property (Collection, List)
	 */
	public static final Byte LIST_PROPERTY = new Byte("2");

	/**
	 * Map property (Map, HashMap)
	 */
	public static final Byte MAP_PROPERTY = new Byte("3");

	/**
	 * Info or Action form property
	 */
	public static final Byte INFO_PROPERTY = new Byte("4");

	/**
	 * Array property (String[], Long[], Byte[] ...)
	 */
	public static final Byte WRAPPER_ARRAY_PROPERTY = new Byte("5");
	/**
	 * Default constructor
	 */
	private BeanUtil() {
		super();
	}

	/**
	 * Copy properties's value from orig to dest object
	 * 
	 * @param dest
	 *            Destination object
	 * @param orig
	 *            Original object
	 */
	public static void copyProperties(Object dest, Object orig) {
		// register converter
		//Utils.registerNullableConverter();
		copyProperties(dest, orig, true);
	}

	/**
	 * Copy properties's value from DynaBean object (orig) to dest object
	 * 
	 * @param dest
	 *            Destination object
	 * @param orig
	 *            Original DynaBean object
	 * @param copyNullValue
	 *            flag indicates may or may not copy null value from orig to
	 *            dest
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private static void copyProperties(Object dest, DynaBean orig,
			boolean copyNullValue) throws IllegalAccessException,
			InvocationTargetException {
		DynaProperty origDescriptors[] = orig.getDynaClass()
				.getDynaProperties();
		for (int i = 0; i < origDescriptors.length; i++) {
			String name = origDescriptors[i].getName();
			if (PropertyUtils.isWriteable(dest, name)) {
				Object value = orig.get(name);
				if (null == value && !copyNullValue) {
					continue;
				}
				copyProperty(dest, name, value);
			}
		}
	}

	/**
	 * Copy properties's value from Map object (orig) to dest object
	 * 
	 * @param dest
	 *            Destination object
	 * @param orig
	 *            Original Map object
	 * @param copyNullValue
	 *            flag indicates may or may not copy null value from orig to
	 *            dest
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private static void copyProperties(Object dest, Map orig,
			boolean copyNullValue) throws InvocationTargetException {
		
		Iterator names = orig.keySet().iterator();
		while (names.hasNext()) {
			String name = (String) names.next();
			Object value = null;
			try {
				if (PropertyUtils.isWriteable(dest, name)) {
					value = orig.get(name);
					// Get dest's property
					PropertyDescriptor propDest = PropertyUtils
							.getPropertyDescriptor(dest, name);
					if (propDest != null) {
						// Check value and copyNullValue flag.
						// If value is null or empty list or empty map
						// and copyNullValue flag is false, so
						// we dont't copy from orig to dest
						if (isNullValue(value) && !copyNullValue) {
							continue;
						}
						// Look up converter
						Converter converter = ConvertUtils.lookup(propDest.getPropertyType());
						// Check converter. Is it exist?
						// Check value. Is it null?
						// Check orig's property type and dest's property type
						if (null != converter
								|| value == null
								|| propDest.getPropertyType() == value
										.getClass()) {
							// Converter exist or property is "simple"
							// or value is null so copy from orig to dest
							copyProperty(dest, name, value);
						} else if (propDest.getPropertyType().isAssignableFrom(
								value.getClass())) {
							// Orig's property is sub class of dest's property,
							// so copy from orig to dest
							copyProperty(dest, name, value);
						} else if (isInfoProperty(propDest.getPropertyType())) {
							// This property is "info" or "form" property,
							// so continues copy all nested properties
							if (null == PropertyUtils.getProperty(dest, name)) {
								copyProperty(dest, name, propDest
										.getPropertyType().newInstance());
							}
							copyProperties(PropertyUtils.getSimpleProperty(
									dest, name), value);
						} else {
							// by pass this property
						}
					}
				}
			} catch (NoSuchMethodException ex) {
				 // Should not happen
			} catch (InstantiationException ex) {
				throw new InvocationTargetException(ex);
			} catch (Exception ex) {
				throw new InvocationTargetException(ex);
			}
		}
	}
	/**
	 * Copy properties's value from orig to dest object
	 * 
	 * @param dest
	 *            Destination object
	 * @param orig
	 *            Original object
	 * @param copyNullValue
	 *            flag indicates may or may not copy null value from orig to
	 *            dest
	 */
	public static void copyProperties(Object dest, Object orig,
			boolean copyNullValue) {
		// register converter
		//Utils.registerNullableConverter();

		// Validate existence of the specified beans
		if (dest == null) {
			throw new IllegalArgumentException("No destination bean specified");
		}
		if (orig == null) {
			throw new IllegalArgumentException("No origin bean specified");
		}
		try {
			if (orig instanceof DynaBean) {
				// orig object is DynaBean object
				copyProperties(dest, (DynaBean) orig, copyNullValue);
			} else if (orig instanceof Map) {
				// orig object is Map object
				copyProperties(dest, (Map) orig, copyNullValue);
			} else {
				// orig is a standard JavaBean
				PropertyDescriptor origDescriptors[] = PropertyUtils
						.getPropertyDescriptors(orig);
				for (int i = 0; i < origDescriptors.length; i++) {
					String name = origDescriptors[i].getName();
					if ("class".equals(name)) {
						continue; // No point in trying to set an object's
						// class
					}
					Object value = null;
					if (PropertyUtils.isReadable(orig, name)
							&& PropertyUtils.isWriteable(dest, name)) {
						value = PropertyUtils.getSimpleProperty(orig, name);
						// get orig's property and dest's property
						PropertyDescriptor propDest = PropertyUtils
								.getPropertyDescriptor(dest, name);
						PropertyDescriptor propSrc = PropertyUtils
								.getPropertyDescriptor(orig, name);
						if (propDest != null) {
							// Check value and copyNullValue flag.
							// If value is null or empty list or empty map
							// and copyNullValue flag is false, so
							// we dont't copy from orig to dest
							if (isNullValue(value) && !copyNullValue) {
								continue;
							}
							// Lookup converter
							Converter converter = ConvertUtils.lookup(propDest.getPropertyType());
							// Check converter. Is it exist?
							// Check value. Is it null?
							// Check orig's property type and dest's property
							// type
							if (null != converter
									|| null == value
									|| propDest.getPropertyType() == propSrc
											.getPropertyType()) {
								// Converter exist or property is "simple"
								// or value is null so copy from orig to dest
								copyProperty(dest, name, value);
							} else if (propDest
									.getPropertyType()
									.isAssignableFrom(propSrc.getPropertyType())) {
								// Orig's property is sub class of dest's
								// property, so copy from orig to dest
								copyProperty(dest, name, value);
							} else if (isInfoProperty(propDest
									.getPropertyType())) {
								// This property is "info" or "form"
								// property so continues copy all nested
								// properties
								if (null == PropertyUtils.getProperty(dest,
										name)) {
									copyProperty(dest, name, propDest
											.getPropertyType().newInstance());
								}
								copyProperties(PropertyUtils.getSimpleProperty(
										dest, name), value);
							} else {
								// by pass this property
							}
						}
					}
				}
			}
		} catch (NoSuchMethodException ex) {
			throw new RuntimeException(ex);
		} catch (InstantiationException ex) {
			throw new RuntimeException(ex);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}
	/**
	 * Copy value from <code>aSrc</code> to <code>aDest</code> with
	 * specified properties in <code>aProperties</code> object
	 * 
	 * @param aDest
	 *            destination object
	 * @param aSrc
	 *            source object
	 * @param aProperties
	 *            class contains property mapping from source to destination
	 *            object
	 * 
	 */
	public static void copyProperties(Object aDest, Object aSrc,
			Properties aProperties) {
		// register converter
		//Utils.registerNullableConverter();

		// validate aProperties parameter
		if (null == aProperties) {
			throw new IllegalArgumentException("Properties does not specified");
		}
		// Validate existence of the specified beans
		if (null == aDest) {
			throw new IllegalArgumentException("No destination bean specified");
		}
		if (null == aSrc && aProperties.getPropertyValues().isEmpty()) {
			throw new IllegalArgumentException("No origin bean specified");
		}
		if (null != aProperties) {
			Map properties = aProperties.getProperties();
			for (Iterator iter = properties.keySet().iterator(); iter.hasNext();) {
				String destPropName = (String) iter.next();
				NameProperty property = (NameProperty) properties
						.get(destPropName);
				String srcPropName = property.getSrcProp();
				Object value = null;
				ValueProperty valueProp = null;
				try {
					valueProp = aProperties.getValue(destPropName);
					boolean notCopyNull = false;
					if (null == valueProp) {
						value = getPropertyValue(aSrc, srcPropName);
						notCopyNull = property.isNotCopyIfNull();
					} else {
						notCopyNull = valueProp.isNotCopyIfNull();
						value = valueProp.getValue();
					}
					initInstance(aDest, destPropName,false);
					if (null == value && notCopyNull) {
						continue;
					}
					copyProperty(aDest, destPropName, value);
				} catch (IllegalAccessException ex) {
					throw new RuntimeException(ex);
				} catch (InvocationTargetException ex) {
					throw new RuntimeException(ex);
				}
			}
		}
	}
	
	/**
	 * Copy value from <code>aSrc</code> to <code>aDest</code> with
	 * specified properties in <code>aProperties</code> object
	 * 
	 * @param aDest
	 *            destination object
	 * @param aProperties
	 *            class contains property mapping from source to destination
	 *            object
	 */
	public static void copyProperties(Object aDest, Properties aProperties) {
		// register converter
		//Utils.registerNullableConverter();
		copyProperties(aDest, null, aProperties);
	}

	/**
	 * Check the given clazz represents "simple" property i.e primitive or
	 * primitive wrapper or String type
	 * @param clazz 
	 * @return boolean
	 */
	private static boolean isSimpleProperty(Class clazz) {
		if (clazz.isPrimitive() || isPrimitiveWrapper(clazz)
				|| String.class == clazz) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the given class represents the "special" property i.e Date,
	 * File, Timestamp, URL, Time
	 * @param clazz 
	 * @return boolean
	 */
	private static boolean isSpecialProperty(Class clazz) {
		if (java.util.Date.class == clazz || File.class == clazz
				|| Timestamp.class == clazz || URL.class == clazz
				|| Time.class == clazz) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the given class represents the array of primitve <br>
	 * boolean[], byte[], short[], int[], long[], double[], float[], char[]
	 * @param clazz 
	 * @return boolean
	 */
	private static boolean isPrimitiveArray(Class clazz) {
		if (boolean[].class == clazz || byte[].class == clazz
				|| short[].class == clazz || int[].class == clazz
				|| long[].class == clazz || double[].class == clazz
				|| float[].class == clazz || char[].class == clazz) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the given class represents the primitve wrapper Boolean, Byte,
	 * Short, Integer, Long, Double, Float, Character ...
	 * @param clazz 
	 * @return boolean
	 */
	private static boolean isPrimitiveWrapper(Class clazz) {
		if (Boolean.class == clazz || Byte.class == clazz
				|| Short.class == clazz || Integer.class == clazz
				|| Long.class == clazz || Double.class == clazz
				|| Float.class == clazz || Character.class == clazz
				|| BigInteger.class == clazz || BigDecimal.class == clazz) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the given class represents the array of primitve wrapper
	 * Boolean[], Byte[], Short[], Integer[], Long[], Double[], Float[],
	 * Character[], String[], BigInteger[], BigDecimal []
	 * @param clazz 
	 * @return boolean
	 */
	private static boolean isPrimitiveWrapperArray(Class clazz) {
		if (String[].class == clazz || Boolean[].class == clazz
				|| Byte[].class == clazz || Short[].class == clazz
				|| Integer[].class == clazz || Long[].class == clazz
				|| Double[].class == clazz || Float[].class == clazz
				|| Character[].class == clazz || BigInteger[].class == clazz
				|| BigDecimal[].class == clazz) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the given clazz represents list class or list interface i.e
	 * List, Collection
	 * 
	 * @param clazz
	 * @return boolean
	 */
	public static boolean isListProperty(Class clazz) {
		// check clazz. Is it index or map class?
		if (clazz == Collection.class || clazz == List.class) {
			return true;
		}
		Class[] interfaces = clazz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			if (Collection.class == interfaces[i]
					|| List.class == interfaces[i]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check if the given class represents Set interface or Set class
	 * 
	 * @param clazz
	 * @return boolean
	 */
	public static boolean isSetProperty(Class clazz) {
		// check clazz. Is it index or map class?
		if (clazz == Set.class || clazz == SortedSet.class) {
			return true;
		}
		Class[] interfaces = clazz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			if (Set.class == interfaces[i] || SortedSet.class == interfaces[i]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check if the given clazz represents map class or map interface i.e Map,
	 * SortedMap
	 * 
	 * @param clazz
	 * @return boolean
	 */
	public static boolean isMapProperty(Class clazz) {
		if (null == clazz) {
			return false;
		}
		if (Map.class == clazz || SortedMap.class == clazz) {
			return true;
		}
		Class[] interfaces = clazz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			if (Map.class == interfaces[i] || SortedMap.class == interfaces[i]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check if the given clazz represents "info" class
	 * 
	 * @deprecated
	 * @param clazz
	 * @return boolean
	 */
	public static boolean isInfoProperty(Class clazz) {
		if (clazz.isInterface() || isPrimitiveWrapperArray(clazz)
				|| isPrimitiveArray(clazz) || isSimpleProperty(clazz)
				|| isMapProperty(clazz) || isListProperty(clazz)
				|| isSetProperty(clazz) || isSpecialProperty(clazz)) {
			return false;
		}
		return true;
	}

	/**
	 * Check null property.
	 * 
	 * @param value
	 * @return Return true if value is null or empty, otherwise return false
	 */
	public static boolean isNullValue(Object value) {
		// check value. Is it null or empty list or empty map or empty array?
		if (value == null || "".equals(value.toString())) {
			return true;
		} else if ((isListProperty(value.getClass()) || isSetProperty(value
				.getClass()))
				&& ((Collection) value).isEmpty()) {
			return true;
		} else if (isMapProperty(value.getClass()) && ((Map) value).isEmpty()) {
			return true;
		} else if (isPrimitiveWrapperArray(value.getClass())
				&& ((Object[]) value).length == 0) {
			return true;
		}
		return false;
	}
	/**
	 * Get property value of <code>aObject</code>
	 * 
	 * @param anObject
	 *            target object
	 * @param aPropName
	 *            property name
	 * @return property value
	 */
	public static Object getPropertyValue(Object anObject, String aPropName) {
		
		Object value = null;

		try {
			value = PropertyUtils.getProperty(anObject, aPropName);
		} catch (IllegalAccessException e) {
			 // by pass
		} catch (InvocationTargetException e) {
			 // by pass
			
		} catch (NoSuchMethodException e) {
			throw new RuntimeException("Property:" + aPropName
					+ " does not found on class:"
					+ anObject.getClass().getName(), e);
			
		} catch (Exception e) {
			 // by pass
		}
		return value;
	}
	/**
	 * Check the existance of property of <code>anObject</code>
	 * 
	 * @param anObject
	 *            target object
	 * @param aPropName
	 *            nested property name
	 * @return true: <code>aPropName</code> exist false:
	 *         <code>aPropName</code> does not exist
	 */

	public static boolean isExistProperty(Object anObject, String aPropName) {
		if (null == anObject) {
			throw new IllegalArgumentException(
					"Parameter [aObject] cannot be null.");
		} else if (Utils.isNullEmpty(aPropName)) {
			throw new IllegalArgumentException(
					"Parameter [aPropName] cannot be null.");
		}
		return isExistProperty(anObject.getClass(), aPropName);
	}

	/**
	 * Check the existance property of <code>aClass</code>
	 * 
	 * @param aClass
	 * @param aPropName
	 * @return true:exist false:not exist
	 */

	public static boolean isExistProperty(Class aClass, String aPropName) {
		if (Utils.isNullEmpty(aPropName)) {
			throw new RuntimeException("Parameter [aPropName] cannot be null.");
		}
		return isExistProperty(aClass, Utils
				.tokensToList(aPropName, "."));
	}

	/**
	 * Check the existance property of <code>aClass</code>
	 * 
	 * @param aClass
	 * @param aPropNameList
	 * @return true:exist false:not exist
	 */

	private static boolean isExistProperty(Class aClass, List aPropNameList) {
		if (null == aClass) {
			throw new RuntimeException("Parameter [aClass] cannot be null.");
		}
		if (aPropNameList.isEmpty()) {
			return true;
		}
		PropertyDescriptor propDesc = getPropertyDescriptor(aClass,
				(String) aPropNameList.remove(0));
		if (null == propDesc) {
			return false;
		}
		return isExistProperty(propDesc.getPropertyType(), aPropNameList);
	}

	/**
	 * 
	 * 
	 * @param aClass
	 * @param aPropName
	 * @return PropertyDescriptor
	 */

	private static PropertyDescriptor getPropertyDescriptor(Class aClass,
			String aPropName) {
		PropertyDescriptor propDescArray[] = PropertyUtils
				.getPropertyDescriptors(aClass);
		for (int i = 0; i < propDescArray.length; i++) {
			if (propDescArray[i].getName().equals(aPropName)) {
				return propDescArray[i];
			}
		}
		return null;
	}
	/**
	 * Set value to property
	 * 
	 * @param anObject
	 *            target object
	 * @param aPropName
	 *            nested property name
	 * @param aValue
	 * 			value to set
	 * @param isInitNestedIfNull
	 *            true: init nested instance if it is null, otherwise init new
	 *            instance 
	 *  @param isInitIfNull
	 *            true: init nested instance if it is null, otherwise init new
	 *            instance 
	 */
	public static void setProperty(Object anObject, String aPropName, Object aValue, boolean isInitNestedIfNull, boolean isInitIfNull ) {
		List propList = Utils.tokensToList(aPropName, ".");
		if (null != propList &&  !propList.isEmpty()) {
			propList.remove(propList.size() -1);
			if (propList != null && propList.size() >0) {
				initInstance (anObject, Utils.listToTokens(propList, "."),isInitNestedIfNull);				
			}
			Object val = getPropertyValue(anObject, aPropName);
			if (!isInitIfNull || (isInitIfNull && null == val)) {				
				try {
					setProperty(anObject,aPropName, aValue);
				} catch (IllegalAccessException e) {
					throw new RuntimeException("Cannot access property " + aPropName
							+ " on class:" + anObject.getClass().getName());
				} catch (InvocationTargetException e) {
					throw new RuntimeException("Cannot access property " + aPropName
							+ " on class:" + anObject.getClass().getName());
				}
				
			}
		}
	}
	/**
	 * Init new instance for nested property name
	 * 
	 * @param anObject
	 *            target object
	 * @param aPropName
	 *            nested property name
	 * @param isInitIfNull
	 *            true: init nested instance if it is null, otherwise init new
	 *            instance 
	 */
	private static void initInstance(Object anObject, String aPropName, boolean isInitIfNull ) {
		String propName = "";
		for (List propList = Utils.tokensToList(aPropName, "."); !propList
				.isEmpty();) {
			if (propName.equals("")) {
				propName = (String) propList.remove(0);
			} else {
				propName = propName + "." + (String) propList.remove(0);
			}
			try {
				if (null == PropertyUtils.getProperty(anObject, propName)) {
					PropertyDescriptor propDesc = PropertyUtils
							.getPropertyDescriptor(anObject, propName);
					if (null == propDesc) {
						throw new RuntimeException("Cannot access property "
								+ propName + " on class:"
								+ anObject.getClass().getName());
					}				
					if (isInfoProperty(propDesc.getPropertyType())) {
						if (!isInitIfNull) {
							setProperty(anObject, propName, propDesc
									.getPropertyType().newInstance());
						} else if (null == getProperty(anObject, propName)){
							setProperty(anObject, propName, propDesc
									.getPropertyType().newInstance());							
						}
					}					
				}
			} catch (IllegalAccessException e) {
				throw new RuntimeException("Cannot access property " + propName
						+ " on class:" + anObject.getClass().getName());
			} catch (InvocationTargetException e) {
				throw new RuntimeException("Cannot access property " + propName
						+ " on class:" + anObject.getClass().getName());
			} catch (NoSuchMethodException e) {
				throw new RuntimeException("Cannot access property " + propName
						+ " on class:" + anObject.getClass().getName());
			} catch (InstantiationException e) {
				throw new RuntimeException(
						"Cannot init new instance for property " + propName
								+ " of class:" + anObject.getClass().getName());
			}
		}
	}
	
	/**
	 * Get all properties of class. Property's information includes:
	 * <ul>
	 * <li>Property name</li>
	 * <li>Property type</li>
	 * The class represents for property
	 * <li>Property kind</li>
	 * 1: "simple" property (i.e primitive, primitive wrapper of array type)
	 * <br>
	 * 2: "list" property (i.e Collection, List, Set, SortedSet ..) <br>
	 * 3: "map" property (i.e Map, SortedMap) <br>
	 * 4: "form" property (sub class of RegisterRoomForm) <br>
	 * </li>
	 * </ul>
	 * 
	 * @param clazz
	 *            "Info" or "Action form" class
	 * @return list of properties
	 */
	private static ArrayList getProperties(Class clazz) {
		ArrayList propertiesList = new ArrayList();
		// check clazz
		if (null != clazz && clazz != Class.class) {
			// Get all fields declared in clazz
			Field[] fields = clazz.getDeclaredFields();
			// Loop on each field
			for (int i = 0; i < fields.length; i++) {
				// property name
				String fieldName = fields[i].getName();
				// property kind
				// 1: simple property (i.e primitive, primitive wrapper of array
				// type)
				// 2: "list" property (i.e Collection, List, Set)
				// 3: "map" property (i.e Map, SortedMap)
				// 4: "form" property (sub class of RegisterRoomForm)
				// 5: "Array" property (wrapper primitive array)
				Byte propertyKind = SIMPLE_PROPERTY;
				if (isSimpleProperty(fields[i].getType())) {
					propertyKind = SIMPLE_PROPERTY;
				} else if (isPrimitiveWrapperArray(fields[i].getType())) {
					propertyKind = WRAPPER_ARRAY_PROPERTY;
				} else if (isListProperty(fields[i].getType())
						|| isSetProperty(fields[i].getType())) {
					propertyKind = LIST_PROPERTY;
				} else if (isMapProperty(fields[i].getType())) {
					propertyKind = MAP_PROPERTY;
				} else if (isInfoProperty(fields[i].getType())) {
					propertyKind = INFO_PROPERTY;
				} else {
					// ignore this property
					continue;
				}
				PropertyInfo propertyInfo = new PropertyInfo(fieldName,
						fields[i].getType(), propertyKind);
				// add to propList
				propertiesList.add(propertyInfo);
			}
			// continue with properties of super class
			propertiesList.addAll(getProperties(clazz.getSuperclass()));
		}
		return propertiesList;
	}
	
	/**
	 * Get all nested properties information.
	 * 
	 * @param clazz
	 *            "Info" or "Action form" class
	 * @param prefixPropertyName
	 *            Front property name. Format xxx.yyy.zzz
	 * @return java.lang.ArrayList List of property's information
	 * @exception Exception
	 */
	public static ArrayList getNestedProperties(Class clazz,
			String prefixPropertyName) throws Exception {
		ArrayList propertiesList = new ArrayList();
		ArrayList clazzPropList = null;
		// get properties of clazz in hashClazzPropList
		synchronized (hashClazzPropList) {
			clazzPropList = (ArrayList) hashClazzPropList.get(clazz);
		}
		// does clazz exist in hashClazzPropList?
		if (clazzPropList != null) {
			for (int i = 0; i < clazzPropList.size(); i++) {
				// get property information in clazzPropList
				PropertyInfo propertyInfo = (PropertyInfo) ((PropertyInfo) clazzPropList
						.get(i)).clone();
				String propertyName = propertyInfo.getPropertyName();
				if (null != prefixPropertyName
						&& !"".equals(prefixPropertyName)) {
					propertyInfo.setPropertyName(prefixPropertyName + "."
							+ propertyName);
				}
				// add to propertiesList
				propertiesList.add(propertyInfo);
				// check property. Is it "info" property?
				if (INFO_PROPERTY.equals(propertyInfo.getPropertyKind())) {
					// make pre property name
					String prePropertiesName = propertyName;
					if (null != prefixPropertyName
							&& !"".equals(prefixPropertyName)) {
						prePropertiesName = prefixPropertyName + "."
								+ propertyName;
					}
					// check this property. Does it's class exist in
					// hashClazzPropList
					ArrayList propSubList = null;
					synchronized (hashClazzPropList) {
						propSubList = (ArrayList) hashClazzPropList
								.get(propertyInfo.getPropertyType());
					}
					if (null != propSubList && !propSubList.isEmpty()) {
						for (int j = 0; j < propSubList.size(); j++) {
							PropertyInfo nestedPropertyInfo = (PropertyInfo) ((PropertyInfo) propSubList
									.get(j)).clone();
							String nestedPropertyName = nestedPropertyInfo
									.getPropertyName();
							nestedPropertyInfo
									.setPropertyName(prePropertiesName + "."
											+ nestedPropertyName);
							propertiesList.add(nestedPropertyInfo);
						}
					} else {
						propSubList = getNestedProperties(propertyInfo
								.getPropertyType(), prePropertiesName);
						// add into list
						propertiesList.addAll(propSubList);
					}
				}
			}
		} else {
			// clazz does not exits in hashClazzPropList map
			// so get all properties of clazz and save into hashClazzPropList
			// map
			ArrayList propList = getProperties(clazz);
			synchronized (hashClazzPropList) {
				hashClazzPropList.put(clazz, propList);
			}
			// get properties of ActionForm
			propertiesList = getNestedProperties(clazz, prefixPropertyName);
		}
		return propertiesList;
	}
	
	/**
	 * Init all nested properties in <code>anObject</code>.<BR>
	 * 
	 * @param anObject
	 *            root object
	 * @param isInitNotNull
	 *            true: init nested instance if it is null, otherwise init new
	 *            instance
	 */
	public static void initNestedInstances(Object anObject,
			boolean isInitNotNull) {
		if (null != anObject) {
			try {
				// Get all properties information
				ArrayList nestedPropList = BeanUtil.getNestedProperties(
						anObject.getClass(), "");
				// Loop on each property
				for (int i = 0; i < nestedPropList.size(); i++) {
					PropertyInfo propertyInfo = (PropertyInfo) nestedPropList
							.get(i);
					if (BeanUtil.INFO_PROPERTY.equals(propertyInfo
							.getPropertyKind())) {
						String fieldName = propertyInfo.getPropertyName();
						Class clazz = propertyInfo.getPropertyType();
						if (!Class.class.getName().equals(clazz.getName())) {
							if (!isInitNotNull) {
								PropertyUtils.setProperty(anObject, fieldName,
										clazz.newInstance());
							} else if (isInitNotNull
									&& BeanUtil.getProperty(anObject,
											fieldName) == null) {
								PropertyUtils.setProperty(anObject, fieldName,
										clazz.newInstance());
							}
						}
					}
				}
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
	}
}